/*******************************************************************************
 * Copyright (c) 2000, 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.internal.ide.dialogs;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;

import javax.xml.parsers.ParserConfigurationException;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.ide.IDEInternalWorkbenchImages;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
import org.eclipse.ui.part.EditorPart;
import org.xml.sax.SAXException;

/**
 * A "fake" editor to show a welcome page
 * The contents of this page are supplied in the product configuration
 *
 * PRIVATE
 *		This class is internal to the workbench and must not be called outside the workbench
 */
public class WelcomeEditor extends EditorPart {

    private final static int HORZ_SCROLL_INCREMENT = 20;

    private final static int VERT_SCROLL_INCREMENT = 20;

    // width at which wrapping will stop and a horizontal scroll bar will be 
    // introduced
    private final static int WRAP_MIN_WIDTH = 150;

    private Composite editorComposite;

    private Cursor handCursor;

    private Cursor busyCursor;

    private WelcomeParser parser;

    private ArrayList hyperlinkRanges = new ArrayList();

    private ArrayList texts = new ArrayList();

    private ScrolledComposite scrolledComposite;

    private IPropertyChangeListener colorListener;

    private boolean mouseDown = false;

    private boolean dragEvent = false;

    private StyledText firstText, lastText;

    private StyledText lastNavigatedText, currentText;

    private boolean nextTabAbortTraversal, previousTabAbortTraversal = false;

    private WelcomeEditorCopyAction copyAction;

    /**
     * Create a new instance of the welcome editor
     */
    public WelcomeEditor() {
        super();
        setPartName(IDEWorkbenchMessages.WelcomeEditor_title);
        copyAction = new WelcomeEditorCopyAction(this);
        copyAction.setEnabled(false);
    }

    /**
     * Update the welcome page to start at the
     * beginning of the text.
     */
    private void focusOn(StyledText newText, int caretOffset) {
        if (newText == null) {
			return;
		}
        newText.setFocus();
        newText.setCaretOffset(caretOffset);
        scrolledComposite.setOrigin(0, newText.getLocation().y);
    }

    /**
     * Finds the next text 
     */
    private StyledText nextText(StyledText text) {
        int index = 0;
        if (text == null) {
			return (StyledText) texts.get(0);
		} else {
			index = texts.indexOf(text);
		}

        //If we are not at the end....
        if (index < texts.size() - 1) {
			return (StyledText) texts.get(index + 1);
		} else {
			return (StyledText) texts.get(0);
		}
    }

    /**
     * Finds the previous text 
     */
    private StyledText previousText(StyledText text) {
        int index = 0;
        if (text == null) {
			return (StyledText) texts.get(0);
		} else {
			index = texts.indexOf(text);
		}

        //If we are at the beginning....
        if (index == 0) {
			return (StyledText) texts.get(texts.size() - 1);
		} else {
			return (StyledText) texts.get(index - 1);
		}
    }

    /**
     * Returns the current text.
     */
    protected StyledText getCurrentText() {
        return currentText;
    }

    /**
     * Returns the copy action. 
     */
    protected WelcomeEditorCopyAction getCopyAction() {
        return copyAction;
    }

    /**
     * Finds the next link after the current selection.
     */
    private StyleRange findNextLink(StyledText text) {
        if (text == null) {
			return null;
		}

        WelcomeItem item = (WelcomeItem) text.getData();
        StyleRange[] ranges = text.getStyleRanges();
        int currentSelectionEnd = text.getSelection().y;

        for (int i = 0; i < ranges.length; i++) {
            if (ranges[i].start >= currentSelectionEnd) {
				if (item.isLinkAt(ranges[i].start)) {
					return ranges[i];
				}
			}
        }
        return null;
    }

    /**
     * Finds the previous link before the current selection.
     */
    private StyleRange findPreviousLink(StyledText text) {
        if (text == null) {
			return null;
		}

        WelcomeItem item = (WelcomeItem) text.getData();
        StyleRange[] ranges = text.getStyleRanges();
        int currentSelectionStart = text.getSelection().x;

        for (int i = ranges.length - 1; i > -1; i--) {
            if ((ranges[i].start + ranges[i].length) < currentSelectionStart) {
				if (item.isLinkAt(ranges[i].start + ranges[i].length - 1)) {
					return ranges[i];
				}
			}
        }
        return null;
    }

    /**
     * Finds the current link of the current selection.
     */
    protected StyleRange getCurrentLink(StyledText text) {
        StyleRange[] ranges = text.getStyleRanges();
        int currentSelectionEnd = text.getSelection().y;
        int currentSelectionStart = text.getSelection().x;

        for (int i = 0; i < ranges.length; i++) {
            if ((currentSelectionStart >= ranges[i].start)
                    && (currentSelectionEnd <= (ranges[i].start + ranges[i].length))) {
                return ranges[i];
            }
        }
        return null;
    }

    /**
     * Adds listeners to the given styled text
     */
    private void addListeners(StyledText styledText) {
        styledText.addMouseListener(new MouseAdapter() {
            public void mouseDown(MouseEvent e) {
                if (e.button != 1) {
                    return;
                }
                mouseDown = true;
            }

            public void mouseUp(MouseEvent e) {
                mouseDown = false;
                StyledText text = (StyledText) e.widget;
                WelcomeItem item = (WelcomeItem) e.widget.getData();
                int offset = text.getCaretOffset();
                if (dragEvent) {
                    dragEvent = false;
                    if (item.isLinkAt(offset)) {
                        text.setCursor(handCursor);
                    }
                } else if (item.isLinkAt(offset)) {
                    text.setCursor(busyCursor);
                    if (e.button == 1) {
                        item.triggerLinkAt(offset);
                        StyleRange selectionRange = getCurrentLink(text);
                        text.setSelectionRange(selectionRange.start,
                                selectionRange.length);
                        text.setCursor(null);
                    }
                }
            }
        });

        styledText.addMouseMoveListener(new MouseMoveListener() {
            public void mouseMove(MouseEvent e) {
                // Do not change cursor on drag events
                if (mouseDown) {
                    if (!dragEvent) {
                        StyledText text = (StyledText) e.widget;
                        text.setCursor(null);
                    }
                    dragEvent = true;
                    return;
                }
                StyledText text = (StyledText) e.widget;
                WelcomeItem item = (WelcomeItem) e.widget.getData();
                int offset = -1;
                try {
                    offset = text.getOffsetAtLocation(new Point(e.x, e.y));
                } catch (IllegalArgumentException ex) {
                    // location is not over a character
                }
                if (offset == -1) {
					text.setCursor(null);
				} else if (item.isLinkAt(offset)) {
					text.setCursor(handCursor);
				} else {
					text.setCursor(null);
				}
            }
        });

        styledText.addTraverseListener(new TraverseListener() {
            public void keyTraversed(TraverseEvent e) {
                StyledText text = (StyledText) e.widget;

                switch (e.detail) {
                case SWT.TRAVERSE_ESCAPE:
                    e.doit = true;
                    break;
                case SWT.TRAVERSE_TAB_NEXT:
                    // Handle Ctrl-Tab
                    if ((e.stateMask & SWT.CTRL) != 0) {
                        if (e.widget == lastText) {
							return;
						} else {
                            e.doit = false;
                            nextTabAbortTraversal = true;
                            lastText.traverse(SWT.TRAVERSE_TAB_NEXT);
                            return;
                        }
                    }
                    if (nextTabAbortTraversal) {
                        nextTabAbortTraversal = false;
                        return;
                    }
                    // Find the next link in current widget, if applicable
                    // Stop at top of widget
                    StyleRange nextLink = findNextLink(text);
                    if (nextLink == null) {
                        // go to the next widget, focus at beginning
                        StyledText nextText = nextText(text);
                        nextText.setSelection(0);
                        focusOn(nextText, 0);
                    } else {
                        // focusOn: allow none tab traversals to align
                        focusOn(text, text.getSelection().x);
                        text.setSelectionRange(nextLink.start, nextLink.length);
                    }
                    e.detail = SWT.TRAVERSE_NONE;
                    e.doit = true;
                    break;
                case SWT.TRAVERSE_TAB_PREVIOUS:
                    // Handle Ctrl-Shift-Tab
                    if ((e.stateMask & SWT.CTRL) != 0) {
                        if (e.widget == firstText) {
							return;
						} else {
                            e.doit = false;
                            previousTabAbortTraversal = true;
                            firstText.traverse(SWT.TRAVERSE_TAB_PREVIOUS);
                            return;
                        }
                    }
                    if (previousTabAbortTraversal) {
                        previousTabAbortTraversal = false;
                        return;
                    }
                    // Find the previous link in current widget, if applicable
                    // Stop at top of widget also
                    StyleRange previousLink = findPreviousLink(text);
                    if (previousLink == null) {
                        if (text.getSelection().x == 0) {
                            // go to the previous widget, focus at end
                            StyledText previousText = previousText(text);
                            previousText.setSelection(previousText
                                    .getCharCount());
                            previousLink = findPreviousLink(previousText);
                            if (previousLink == null) {
								focusOn(previousText, 0);
							} else {
                                focusOn(previousText, previousText
                                        .getSelection().x);
                                previousText
                                        .setSelectionRange(previousLink.start,
                                                previousLink.length);
                            }
                        } else {
                            // stay at top of this widget
                            focusOn(text, 0);
                        }
                    } else {
                        // focusOn: allow none tab traversals to align
                        focusOn(text, text.getSelection().x);
                        text.setSelectionRange(previousLink.start,
                                previousLink.length);
                    }
                    e.detail = SWT.TRAVERSE_NONE;
                    e.doit = true;
                    break;
                default:
                    break;
                }
            }
        });

        styledText.addKeyListener(new KeyListener() {
            public void keyReleased(KeyEvent e) {
                //Ignore a key release
            }

            public void keyPressed(KeyEvent event) {
                StyledText text = (StyledText) event.widget;
                if (event.character == ' ' || event.character == SWT.CR) {
                    if (text != null) {
                        WelcomeItem item = (WelcomeItem) text.getData();

                        //Be sure we are in the selection
                        int offset = text.getSelection().x + 1;

                        if (item.isLinkAt(offset)) {
                            text.setCursor(busyCursor);
                            item.triggerLinkAt(offset);
                            StyleRange selectionRange = getCurrentLink(text);
                            text.setSelectionRange(selectionRange.start,
                                    selectionRange.length);
                            text.setCursor(null);
                        }
                    }
                    return;
                }

                // When page down is pressed, move the cursor to the next item in the 
                // welcome page.   Note that this operation wraps (pages to the top item
                // when the last item is reached).
                if (event.keyCode == SWT.PAGE_DOWN) {
                    focusOn(nextText(text), 0);
                    return;
                }

                // When page up is pressed, move the cursor to the previous item in the 
                // welcome page.  Note that this operation wraps (pages to the bottom item
                // when the first item is reached).
                if (event.keyCode == SWT.PAGE_UP) {
                    focusOn(previousText(text), 0);
                    return;
                }
            }
        });

        styledText.addFocusListener(new FocusAdapter() {
            public void focusLost(FocusEvent e) {
                // Remember current text widget
                lastNavigatedText = (StyledText) e.widget;
            }

            public void focusGained(FocusEvent e) {
                currentText = (StyledText) e.widget;

                // Remove highlighted selection if text widget has changed
                if ((currentText != lastNavigatedText)
                        && (lastNavigatedText != null)) {
					lastNavigatedText.setSelection(lastNavigatedText
                            .getSelection().x);
				}

                // enable/disable copy action
                copyAction.setEnabled(currentText.getSelectionCount() > 0);
            }
        });

        styledText.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                // enable/disable copy action			
                StyledText text = (StyledText) e.widget;
                copyAction.setEnabled(text.getSelectionCount() > 0);
            }
        });
    }

    /**
     * Creates the wizard's title area.
     *
     * @param parent the SWT parent for the title area composite
     * @return the created info area composite
     */
    private Composite createInfoArea(Composite parent) {
        // Create the title area which will contain
        // a title, message, and image.
        this.scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL
                | SWT.H_SCROLL);
        this.scrolledComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
        final Composite infoArea = new Composite(this.scrolledComposite,
                SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.marginHeight = 10;
        layout.verticalSpacing = 5;
        layout.numColumns = 2;
        infoArea.setLayout(layout);
        GridData data = new GridData(GridData.FILL_BOTH);
        infoArea.setLayoutData(data);
        boolean wrapped = parser.isFormatWrapped();
        int HINDENT = 20;

        // Get the background color for the title area
        Display display = parent.getDisplay();
        Color background = JFaceColors.getBannerBackground(display);
        Color foreground = JFaceColors.getBannerForeground(display);
        infoArea.setBackground(background);

        int textStyle = SWT.MULTI | SWT.READ_ONLY;
        if (wrapped) {
            textStyle = textStyle | SWT.WRAP;
        }
        StyledText sampleStyledText = null;
        // Create the intro item
        WelcomeItem item = getIntroItem();
        if (item != null) {
            StyledText styledText = new StyledText(infoArea, textStyle);
            this.texts.add(styledText);
            sampleStyledText = styledText;
            styledText.setCursor(null);
            JFaceColors.setColors(styledText, foreground, background);
            styledText.setText(getIntroItem().getText());
            setBoldRanges(styledText, item.getBoldRanges());
            setLinkRanges(styledText, item.getActionRanges());
            setLinkRanges(styledText, item.getHelpRanges());
            GridData gd = new GridData(GridData.FILL_HORIZONTAL);
            gd.horizontalSpan = 2;
            gd.horizontalIndent = HINDENT;
            gd.verticalAlignment = GridData.VERTICAL_ALIGN_BEGINNING;
            styledText.setLayoutData(gd);
            styledText.setData(item);
            addListeners(styledText);

            Label spacer = new Label(infoArea, SWT.NONE);
            spacer.setBackground(background);
            gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING);
            gd.horizontalSpan = 2;
            spacer.setLayoutData(gd);
        }
        firstText = sampleStyledText;

        // Create the welcome items
        Label imageLabel = null;
        WelcomeItem[] items = getItems();
        for (int i = 0; i < items.length; i++) {
            Label label = new Label(infoArea, SWT.NONE);
            label.setBackground(background);
            label
                    .setImage(PlatformUI
                            .getWorkbench()
                            .getSharedImages()
                            .getImage(
                                    IDEInternalWorkbenchImages.IMG_OBJS_WELCOME_ITEM));
            GridData gd = new GridData();
            gd.horizontalIndent = HINDENT;
            gd.verticalAlignment = GridData.VERTICAL_ALIGN_BEGINNING;
            label.setLayoutData(gd);
            if (imageLabel == null) {
                imageLabel = label;
            }

            StyledText styledText = new StyledText(infoArea, textStyle);
            this.texts.add(styledText);
            sampleStyledText = styledText;
            styledText.setCursor(null);
            JFaceColors.setColors(styledText, foreground, background);
            styledText.setText(items[i].getText());
            setBoldRanges(styledText, items[i].getBoldRanges());
            setLinkRanges(styledText, items[i].getActionRanges());
            setLinkRanges(styledText, items[i].getHelpRanges());
            gd = new GridData(GridData.FILL_HORIZONTAL);
            gd.verticalAlignment = GridData.VERTICAL_ALIGN_BEGINNING;
            gd.verticalSpan = 2;
            styledText.setLayoutData(gd);
            styledText.setData(items[i]);
            addListeners(styledText);

            Label spacer = new Label(infoArea, SWT.NONE);
            spacer.setBackground(background);
            gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING);
            gd.horizontalSpan = 2;
            spacer.setLayoutData(gd);

            // create context menu
            MenuManager menuMgr = new MenuManager("#PopUp"); //$NON-NLS-1$
            menuMgr.add(copyAction);
            styledText.setMenu(menuMgr.createContextMenu(styledText));
        }

        lastText = sampleStyledText;
        this.scrolledComposite.setContent(infoArea);
        Point p = infoArea.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
        this.scrolledComposite.setMinHeight(p.y);
        if (wrapped) {
            // introduce a horizontal scroll bar after a minimum width is reached
            this.scrolledComposite.setMinWidth(WRAP_MIN_WIDTH);
        } else {
            this.scrolledComposite.setMinWidth(p.x);
        }
        this.scrolledComposite.setExpandHorizontal(true);
        this.scrolledComposite.setExpandVertical(true);

        // When the welcome editor is resized, we need to set the width hint for
        // wrapped StyledText widgets so that the wrapped height will be recalculated.
        if (wrapped && (imageLabel != null)) {
            // figure out how wide the StyledText widgets should be, do this by first
            // calculating the width of the area not used by styled text widgets
            Rectangle bounds = imageLabel.getBounds();
            final int adjust = HINDENT + bounds.width + layout.verticalSpacing
                    + (layout.marginWidth * 2);
            final int adjustFirst = HINDENT + (layout.marginWidth * 2);
            infoArea.addListener(SWT.Resize, new Listener() {
                public void handleEvent(Event event) {
                    int w = scrolledComposite.getClientArea().width;
                    // if the horizontal scroll bar exists, we want to wrap to the
                    // minimum wrap width
                    if (w < WRAP_MIN_WIDTH) {
                        w = WRAP_MIN_WIDTH;
                    }
                    for (int i = 0; i < texts.size(); i++) {
                        int extent;
                        if (i == 0) {
							extent = w - adjustFirst;
						} else {
							extent = w - adjust;
						}
                        StyledText text = (StyledText) texts.get(i);
                        Point p = text.computeSize(extent, SWT.DEFAULT, false);
                        ((GridData) text.getLayoutData()).widthHint = p.x;
                    }
                    // reset the scrolled composite height since the height of the 
                    // styled text widgets have changed
                    Point p = infoArea.computeSize(SWT.DEFAULT, SWT.DEFAULT,
                            true);
                    scrolledComposite.setMinHeight(p.y);
                }
            });
        }

        // Adjust the scrollbar increments
        if (sampleStyledText == null) {
            this.scrolledComposite.getHorizontalBar().setIncrement(
                    HORZ_SCROLL_INCREMENT);
            this.scrolledComposite.getVerticalBar().setIncrement(
                    VERT_SCROLL_INCREMENT);
        } else {
            GC gc = new GC(sampleStyledText);
            int width = gc.getFontMetrics().getAverageCharWidth();
            gc.dispose();
            this.scrolledComposite.getHorizontalBar().setIncrement(width);
            this.scrolledComposite.getVerticalBar().setIncrement(
                    sampleStyledText.getLineHeight());
        }
        return infoArea;
    }

    /**
     * Creates the SWT controls for this workbench part.
     * <p>
     * Clients should not call this method (the workbench calls this method at
     * appropriate times).
     * </p>
     * <p>
     * For implementors this is a multi-step process:
     * <ol>
     *   <li>Create one or more controls within the parent.</li>
     *   <li>Set the parent layout as needed.</li>
     *   <li>Register any global actions with the <code>IActionService</code>.</li>
     *   <li>Register any popup menus with the <code>IActionService</code>.</li>
     *   <li>Register a selection provider with the <code>ISelectionService</code>
     *     (optional). </li>
     * </ol>
     * </p>
     *
     * @param parent the parent control
     */
    public void createPartControl(Composite parent) {
        // read our contents
        readFile();
        if (parser == null) {
			return;
		}

        handCursor = new Cursor(parent.getDisplay(), SWT.CURSOR_HAND);
        busyCursor = new Cursor(parent.getDisplay(), SWT.CURSOR_WAIT);

        editorComposite = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        layout.verticalSpacing = 0;
        layout.horizontalSpacing = 0;
        editorComposite.setLayout(layout);

        createTitleArea(editorComposite);

        Label titleBarSeparator = new Label(editorComposite, SWT.HORIZONTAL
                | SWT.SEPARATOR);
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        titleBarSeparator.setLayoutData(gd);

        createInfoArea(editorComposite);

        getSite().getWorkbenchWindow().getWorkbench().getHelpSystem().setHelp(
				editorComposite, IIDEHelpContextIds.WELCOME_EDITOR);

        this.colorListener = new IPropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent event) {
                if (event.getProperty()
                        .equals(JFacePreferences.HYPERLINK_COLOR)) {
                    Color fg = JFaceColors.getHyperlinkText(editorComposite
                            .getDisplay());
                    Iterator links = hyperlinkRanges.iterator();
                    while (links.hasNext()) {
                        StyleRange range = (StyleRange) links.next();
                        range.foreground = fg;
                    }
                }
            }
        };

        JFacePreferences.getPreferenceStore().addPropertyChangeListener(
                this.colorListener);

    }

    /**
     * Creates the wizard's title area.
     *
     * @param parent the SWT parent for the title area composite
     * @return the created title area composite
     */
    private Composite createTitleArea(Composite parent) {
        // Get the background color for the title area
        Display display = parent.getDisplay();
        Color background = JFaceColors.getBannerBackground(display);
        Color foreground = JFaceColors.getBannerForeground(display);

        // Create the title area which will contain
        // a title, message, and image.
        Composite titleArea = new Composite(parent, SWT.NONE | SWT.NO_FOCUS);
        GridLayout layout = new GridLayout();
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        layout.verticalSpacing = 0;
        layout.horizontalSpacing = 0;
        layout.numColumns = 2;
        titleArea.setLayout(layout);
        titleArea.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        titleArea.setBackground(background);

        // Message label
        final CLabel messageLabel = new CLabel(titleArea, SWT.LEFT) {
            protected String shortenText(GC gc, String text, int width) {
                if (gc.textExtent(text, SWT.DRAW_MNEMONIC).x <= width) {
					return text;
				}
                final String ellipsis = "..."; //$NON-NLS-1$
                int ellipseWidth = gc.textExtent(ellipsis, SWT.DRAW_MNEMONIC).x;
                int length = text.length();
                int end = length - 1;
                while (end > 0) {
                    text = text.substring(0, end);
                    int l1 = gc.textExtent(text, SWT.DRAW_MNEMONIC).x;
                    if (l1 + ellipseWidth <= width) {
                        return text + ellipsis;
                    }
                    end--;
                }
                return text + ellipsis;
            }
        };
        JFaceColors.setColors(messageLabel, foreground, background);
        messageLabel.setText(getBannerTitle());
        messageLabel.setFont(JFaceResources.getHeaderFont());

        final IPropertyChangeListener fontListener = new IPropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent event) {
                if (JFaceResources.HEADER_FONT.equals(event.getProperty())) {
                    messageLabel.setFont(JFaceResources.getHeaderFont());
                }
            }
        };

        messageLabel.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent event) {
                JFaceResources.getFontRegistry().removeListener(fontListener);
            }
        });

        JFaceResources.getFontRegistry().addListener(fontListener);

        GridData gd = new GridData(GridData.FILL_BOTH);
        messageLabel.setLayoutData(gd);

        // Title image
        Label titleImage = new Label(titleArea, SWT.LEFT);
        titleImage.setBackground(background);
        titleImage.setImage(PlatformUI.getWorkbench().getSharedImages()
                .getImage(IDEInternalWorkbenchImages.IMG_OBJS_WELCOME_BANNER));
        gd = new GridData();
        gd.horizontalAlignment = GridData.END;
        titleImage.setLayoutData(gd);

        return titleArea;
    }

    /**
     * The <code>WorkbenchPart</code> implementation of this 
     * <code>IWorkbenchPart</code> method disposes the title image
     * loaded by <code>setInitializationData</code>. Subclasses may extend.
     */
    public void dispose() {
        super.dispose();
        if (busyCursor != null) {
			busyCursor.dispose();
		}
        if (handCursor != null) {
			handCursor.dispose();
		}
        if (this.colorListener != null) {
            JFacePreferences.getPreferenceStore().removePropertyChangeListener(
                    this.colorListener);
        }
    }

    /* (non-Javadoc)
     * Saves the contents of this editor.
     * <p>
     * Subclasses must override this method to implement the open-save-close lifecycle
     * for an editor.  For greater details, see <code>IEditorPart</code>
     * </p>
     *
     * @see IEditorPart
     */
    public void doSave(IProgressMonitor monitor) {
        // do nothing
    }

    /* (non-Javadoc)
     * Saves the contents of this editor to another object.
     * <p>
     * Subclasses must override this method to implement the open-save-close lifecycle
     * for an editor.  For greater details, see <code>IEditorPart</code>
     * </p>
     *
     * @see IEditorPart
     */
    public void doSaveAs() {
        // do nothing	
    }

    /**
     * Returns the title obtained from the parser
     */
    private String getBannerTitle() {
        if (parser.getTitle() == null) {
			return ""; //$NON-NLS-1$
		}
        return parser.getTitle();
    }

    /**
     * Returns the intro item or <code>null</code>
     */
    private WelcomeItem getIntroItem() {
        return parser.getIntroItem();
    }

    /**
     * Returns the welcome items
     */
    private WelcomeItem[] getItems() {
        return parser.getItems();
    }

    /* (non-Javadoc)
     * Sets the cursor and selection state for this editor to the passage defined
     * by the given marker.
     * <p>
     * Subclasses may override.  For greater details, see <code>IEditorPart</code>
     * </p>
     *
     * @see IEditorPart
     */
    public void gotoMarker(IMarker marker) {
        // do nothing
    }

    /* (non-Javadoc)
     * Initializes the editor part with a site and input.
     * <p>
     * Subclasses of <code>EditorPart</code> must implement this method.  Within
     * the implementation subclasses should verify that the input type is acceptable
     * and then save the site and input.  Here is sample code:
     * </p>
     * <pre>
     *		if (!(input instanceof IFileEditorInput))
     *			throw new PartInitException("Invalid Input: Must be IFileEditorInput");
     *		setSite(site);
     *		setInput(editorInput);
     * </pre>
     */
    public void init(IEditorSite site, IEditorInput input)
            throws PartInitException {
        if (!(input instanceof WelcomeEditorInput)) {
			throw new PartInitException(
                    "Invalid Input: Must be WelcomeEditorInput"); //$NON-NLS-1$
		}
        setSite(site);
        setInput(input);
    }

    /* (non-Javadoc)
     * Returns whether the contents of this editor have changed since the last save
     * operation.
     * <p>
     * Subclasses must override this method to implement the open-save-close lifecycle
     * for an editor.  For greater details, see <code>IEditorPart</code>
     * </p>
     *
     * @see IEditorPart
     */
    public boolean isDirty() {
        return false;
    }

    /* (non-Javadoc)
     * Returns whether the "save as" operation is supported by this editor.
     * <p>
     * Subclasses must override this method to implement the open-save-close lifecycle
     * for an editor.  For greater details, see <code>IEditorPart</code>
     * </p>
     *
     * @see IEditorPart
     */
    public boolean isSaveAsAllowed() {
        return false;
    }

    /**
     * Read the contents of the welcome page
     * 
     * @param is the <code>InputStream</code> to parse
     * @throws IOException if there is a problem parsing the stream.
     */
    public void read(InputStream is) throws IOException {
        try {
            parser = new WelcomeParser();
        } catch (ParserConfigurationException e) {
            throw (IOException) (new IOException().initCause(e));
        } catch (SAXException e) {
            throw (IOException) (new IOException().initCause(e));
        }
        parser.parse(is);
    }

    /**
     * Reads the welcome file
     */
    public void readFile() {
        URL url = ((WelcomeEditorInput) getEditorInput()).getAboutInfo()
                .getWelcomePageURL();

        if (url == null) {
			// should not happen 
            return;
		}

        InputStream is = null;
        try {
            is = url.openStream();
            read(is);
        } catch (IOException e) {
            IStatus status = new Status(IStatus.ERROR,
                    IDEWorkbenchPlugin.IDE_WORKBENCH, 1, IDEWorkbenchMessages.WelcomeEditor_accessException, e);
            IDEWorkbenchPlugin.log(IDEWorkbenchMessages.WelcomeEditor_readFileError, status);
        } finally {
            try {
                if (is != null) {
					is.close();
				}
            } catch (IOException e) {
            }
        }
    }

    /**
     * Sets the styled text's bold ranges
     */
    private void setBoldRanges(StyledText styledText, int[][] boldRanges) {
        for (int i = 0; i < boldRanges.length; i++) {
            StyleRange r = new StyleRange(boldRanges[i][0], boldRanges[i][1],
                    null, null, SWT.BOLD);
            styledText.setStyleRange(r);
        }
    }

    /**
     * Asks this part to take focus within the workbench.
     * <p>
     * Clients should not call this method (the workbench calls this method at
     * appropriate times).
     * </p>
     */
    public void setFocus() {
        if ((editorComposite != null) && (lastNavigatedText == null)
                && (currentText == null)) {
			editorComposite.setFocus();
		}
    }

    /**
     * Sets the styled text's link (blue) ranges
     */
    private void setLinkRanges(StyledText styledText, int[][] linkRanges) {
        //Color fg = styledText.getDisplay().getSystemColor(SWT.COLOR_BLUE);
        Color fg = JFaceColors.getHyperlinkText(styledText.getShell()
                .getDisplay());
        for (int i = 0; i < linkRanges.length; i++) {
            StyleRange r = new StyleRange(linkRanges[i][0], linkRanges[i][1],
                    fg, null);
            styledText.setStyleRange(r);
            hyperlinkRanges.add(r);
        }
    }

}
